package com.service;

import com.dao.RedPacketDao;
import com.dao.UserRedPacketDao;
import com.pojo.RedPacket;
import com.pojo.UserRedPacket;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundListOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import redis.clients.jedis.Jedis;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;

@Service
public class UserRedPacketServiceImpl implements UserRedPacketService {

    @Autowired
    RedPacketDao redPacketDao = null;

    @Autowired
    UserRedPacketDao userRedPacketDao = null;

    // 失败
    private static final int FAILED = 0;

    //一.使用传统事务，悲观锁 for update
    @Override
    @Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)
    public int grabRedPacket(Long redPacketId, Long userId) {
        //1.获取红包信息
        RedPacket redPacket = redPacketDao.getRedPacket(redPacketId);

        if(redPacket.getStock() <= 0)
            System.out.println("stock=" + redPacket.getStock() + ",redPacketId=" + redPacketId + ",userId=" + userId);

        //2.检查是否有剩余红包
        if (redPacket.getStock() > 0) {
            //3.递减红包数量
            redPacketDao.decreaseRedPacket(redPacketId);
            UserRedPacket userRedPacket = new UserRedPacket();
            userRedPacket.setUserId(userId);
            userRedPacket.setRedPacketId(redPacketId);
            userRedPacket.setAmount(redPacket.getUnitAmount());
            userRedPacket.setNote("抢红包" + redPacket.getId());
            //4.保存红包信息
            int result = userRedPacketDao.grabRedPacket(userRedPacket);
            //事务有效
            //int i = 1/0;
            return result;
        }
        return FAILED;
    }


    /**
     * 二.通过乐观锁 表version控制并发数据问题
     * @param redPacketId
     * @param userId
     * @return
     */
    @Override
    @Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)
    public int grabRedPacketForVersion(Long redPacketId, Long userId) {
        // 记录开始时间
        long start = System.currentTimeMillis();
        // 无限循环，等待成功或者时间满100亳秒退岀
        while (true){
            // 获取循环当前时间
            long end = System.currentTimeMillis();
            // 当前时间己经超过100毫秒，返回失败
            if (end - start > 100) {
                return FAILED;
            }

            //1.获取红包信息
            RedPacket redPacket = redPacketDao.getRedPacket(redPacketId);
            if(redPacket.getStock() <= 0){
                System.out.println("stock=" + redPacket.getStock() + ",redPacketId=" + redPacketId + ",userId=" + userId);
                return FAILED;
            }
            if (redPacket.getStock() > 0) {
                int update = redPacketDao.decreaseRedPacketForVersion(redPacketId, redPacket.getVersion());
                if (update == 0) {
                    continue;
                }
                UserRedPacket packet = new UserRedPacket();
                packet.setUserId(userId);
                packet.setRedPacketId(redPacketId);
                packet.setAmount(redPacket.getUnitAmount());
                packet.setNote("抢红包" + redPacket.getId());
                //4.保存红包信息
                int result = userRedPacketDao.grabRedPacket(packet);
                return result;
            }
        }
    }






    //三.使用Redis高并发读写，MYSQL批量写入
    final String script =
            //1.定义已抢红包用户key,链表
            "local listKey = 'red_packet_list_'..KEYS[1] \n" +
                    //2.定义红包信息key,HashSet
                    "local redPacket = 'red_packet_'..KEYS[1] \n" +
                    //3.读取Redis中红包信息，剩余红包数量
                    "local stock = tonumber(redis.call('hget', redPacket, 'stock')) \n" +
                    "if stock <= 0 then return 0 end \n" +
                    "stock = stock - 1 \n" +
                    //4.保存剩余红包数量
                    "redis.call('hset',redPacket,'stock', tostring(stock)) \n" +
                    //5.保持抢红包用户信息
                    "redis.call('rpush',listKey,ARGV[1]) \n" +
                    //6.根据剩余红包数量，返回相应的值
                    "if stock == 0 then return 2 end \n" +
                    "return 1 \n";

    final static String USER_RED_PACKET_KEY_LIST = "red_packet_list_";
    final String RED_PACKET_KEY = "red_packet_";
    //hmset red_packet_5 stock 100 unit_amount 10
    // 在缓存Lua脚本后，使用该变量保存Redis返回的32位的SHA1编码，使用它去执行缓存的 Lua脚本
    String sha1 = null;

    @Autowired
    RedisTemplate redisTemplate = null;
    @Autowired
    DataSource dataSource;

    @Override
    public Long grabRedPacketByRedis(Long redPacketId, Long userId) {
        //当前请红包用户ID和时间
        String args = userId + "_" + System.currentTimeMillis();
        Long result = null;

        //获取底层Jedis对象
        Jedis jedis = (Jedis) redisTemplate.getConnectionFactory().getConnection().getNativeConnection();

        try {
            if (sha1 == null) {
                sha1 = jedis.scriptLoad(script);
            }
            //执行脚本，返回结果
            Object r = jedis.evalsha(sha1, 1, redPacketId + "", args);
            result = (Long) r;

            if (result == 2) {
                //TODO 需要去同步保存数据
                String amount = jedis.hget(RED_PACKET_KEY + redPacketId, "unit_amount");
                saveUserRedPacketByRedis(redPacketId, Double.parseDouble(amount));
            }
        } finally {
            //确保jedis顺利关闭
            if (jedis.isConnected()) {
                jedis.close();
            }
        }
        return result;
    }

    // 每次取出1000条，避免一次取出消耗太多内存
    private static final int TIME_SIZE = 1000;

    @Async
    public void saveUserRedPacketByRedis(Long redPacketId, Double unitAmount) {
        System.out.println("开始保存数据");
        Long startTime = System.currentTimeMillis();
        //获取列表操作对象
        BoundListOperations boundListOperations = redisTemplate.boundListOps(USER_RED_PACKET_KEY_LIST + redPacketId);
        Long size = boundListOperations.size();
        System.out.println("数据size="+size);
        List<UserRedPacket> userRedPacketList = new ArrayList<>(TIME_SIZE);
        int count = 0;

        Long times = size % TIME_SIZE == 0 ? size / TIME_SIZE : size / TIME_SIZE + 1;
        for (int i = 0; i < times; i++) {
            List list = boundListOperations.range(i * TIME_SIZE, (i + 1) * TIME_SIZE-1);

            userRedPacketList.clear();
            for (int j = 0; j < list.size(); j++) {
                String args = list.get(j).toString();
                String[] split = args.split("_");
                String userIdStr = split[0];
                String timeStr = split[1];

                UserRedPacket userRedPacket = new UserRedPacket();
                userRedPacket.setUserId(Long.parseLong(userIdStr));
                userRedPacket.setRedPacketId(redPacketId);
                userRedPacket.setAmount(unitAmount);
                userRedPacket.setGrabTime(new Timestamp(Long.parseLong(timeStr)));
                userRedPacket.setNote("抢红包" + redPacketId);
                userRedPacketList.add(userRedPacket);
            }
            System.out.println("数据List size="+userRedPacketList.size());
            count += executeBatch(userRedPacketList);
            System.out.println("数据count="+count);
        }
        //删除Redis列表
        redisTemplate.delete(USER_RED_PACKET_KEY_LIST + redPacketId);
        Long endTime = System.currentTimeMillis();
        System.err.println("保存数据结束，耗时" + (endTime - startTime) + "毫秒，共" + count + "条记录被保存。");
    }

    private int executeBatch(List<UserRedPacket> userRedPacketList) {
        Connection connection = null;
        int[] count = null;
        try {
            long startTime = System.currentTimeMillis();
            connection = dataSource.getConnection();
            System.out.println("连接数据库使用时间："+(System.currentTimeMillis() - startTime));
            connection.setAutoCommit(false);
            Statement statement = connection.createStatement();

            for (UserRedPacket userRedPacket : userRedPacketList) {
                String sql1 = "update t_red_packet set stock = stock - 1 where id = " + userRedPacket.getRedPacketId();
                DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                String sql2 = "insert into t_user_red_packet (red_packet_id, user_id, amount, grab_time, note)"
                        + " values (" + userRedPacket.getRedPacketId() + "," + userRedPacket.getUserId() + ", "
                        + userRedPacket.getAmount() + "," + "'" + df.format(userRedPacket.getGrabTime()) + "'" + ","
                        + "'" + userRedPacket.getNote() + "')";
                statement.addBatch(sql1);
                statement.addBatch(sql2);
            }
            count = statement.executeBatch();
            connection.commit();
        } catch (SQLException throwable) {
            throwable.printStackTrace();
            count = null;
        } finally {
            try {
                if (connection != null && !connection.isClosed()) {
                    connection.close();
                }
            } catch (SQLException throwable) {
                throwable.printStackTrace();
            }
        }
        return count == null ? 0 : count.length / 2;
    }
}
